/*
 *    Copyright (c) 2023 Project CHIP Authors
 *    All rights reserved.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#include <platform/silabs/platformAbstraction/SilabsPlatform.h>
#include <sl_si91x_common_flash_intf.h>

#include <FreeRTOS.h>
#include <task.h>

#include <app/icd/server/ICDServerConfig.h>

#include <lib/support/CodeUtils.h>
#if SILABS_LOG_ENABLED
#include "silabs_utils.h"
#endif // SILABS_LOG_ENABLED

#define SOC_PLL_REF_FREQUENCY 40000000 // /* PLL input REFERENCE clock 40MHZ */
// Note: Change this macro to required PLL frequency in hertz
#define PS4_SOC_FREQ 180000000 /* PLL out clock 180MHz */
#define SWITCH_QSPI_TO_SOC_PLL
#define ICACHE_DISABLE
#define DEBUG_DISABLE

/* QSPI clock config params */
#define INTF_PLL_500_CTRL_VALUE 0xD900
#define INTF_PLL_CLK 80000000 /* PLL out clock 80 MHz */

// TODO add includes ?
extern "C" {
#if defined(SL_SI91X_BOARD_INIT)
#include "rsi_board.h"
#endif // SL_SI91X_BOARD_INIT

#include "rsi_rom_clks.h"
#include "sl_si91x_hal_soc_soft_reset.h"

#ifdef SL_CATALOG_SIMPLE_BUTTON_PRESENT
#include "sl_si91x_button.h"
#include "sl_si91x_button_pin_config.h"
#endif // SL_CATALOG_SIMPLE_BUTTON_PRESENT

#ifdef ENABLE_WSTK_LEDS
#if (defined(SL_MATTER_RGB_LED_ENABLED) && SL_MATTER_RGB_LED_ENABLED == 1)
#include "sl_si91x_rgb_led.h"
#include "sl_si91x_rgb_led_config.h"
#include "sl_si91x_rgb_led_instances.h"
#define SL_LED_COUNT SL_SI91X_RGB_LED_COUNT
const sl_rgb_led_t * ledPinArray[SL_LED_COUNT] = { &led_led0 };
#define SL_RGB_LED_INSTANCE(n) (ledPinArray[n])
#else
#include "sl_si91x_led.h"
#include "sl_si91x_led_config.h"
#include "sl_si91x_led_instances.h"
#define SL_LED_COUNT SL_SI91x_LED_COUNT
uint8_t ledPinArray[SL_LED_COUNT] = { SL_LED_LED0_PIN, SL_LED_LED1_PIN };
#endif // (defined(SL_MATTER_RGB_LED_ENABLED) && SL_MATTER_RGB_LED_ENABLED)
#endif // ENABLE_WSTK_LEDS
}

#ifdef SL_CATALOG_SYSTEMVIEW_TRACE_PRESENT
#include "SEGGER_SYSVIEW.h"
#endif

#if SILABS_LOG_OUT_UART || ENABLE_CHIP_SHELL
#include "uart.h"
#endif

namespace chip {
namespace DeviceLayer {
namespace Silabs {
namespace {

#ifdef SL_CATALOG_SIMPLE_BUTTON_PRESENT
uint8_t sButtonStates[SL_SI91x_BUTTON_COUNT] = { 0 };
#endif // SL_CATALOG_SIMPLE_BUTTON_PRESENT

#if CHIP_CONFIG_ENABLE_ICD_SERVER && defined(SL_CATALOG_SIMPLE_BUTTON_PRESENT)
bool btn0_pressed = false;
#endif // CHIP_CONFIG_ENABLE_ICD_SERVER && defined(SL_CATALOG_SIMPLE_BUTTON_PRESENT)

#if CHIP_CONFIG_ENABLE_ICD_SERVER == 0
int soc_pll_config(void)
{
    int32_t status = RSI_OK;

    RSI_CLK_M4SocClkConfig(M4CLK, M4_ULPREFCLK, 0);
    // Configures the required registers for 180 Mhz clock in PS4
    RSI_PS_PS4SetRegisters();
    // Configure the PLL frequency
    // Configure the SOC PLL to 180MHz
    RSI_CLK_SetSocPllFreq(M4CLK, PS4_SOC_FREQ, SOC_PLL_REF_FREQUENCY);
    // Switch M4 clock to PLL clock for speed operations
    RSI_CLK_M4SocClkConfig(M4CLK, M4_SOCPLLCLK, 0);

    SysTick_Config(SystemCoreClock / configTICK_RATE_HZ);

#ifdef SWITCH_QSPI_TO_SOC_PLL
    /* program intf pll to 160Mhz */
    SPI_MEM_MAP_PLL(INTF_PLL_500_CTRL_REG9) = INTF_PLL_500_CTRL_VALUE;
    status                                  = RSI_CLK_SetIntfPllFreq(M4CLK, INTF_PLL_CLK, SOC_PLL_REF_FREQUENCY);
    if (status != RSI_OK)
    {
        ChipLogError(DeviceLayer, "Failed to Config Interface PLL Clock, status: %ld", status);
    }
    else
    {
        ChipLogProgress(DeviceLayer, "Configured Interface PLL Clock to %d", INTF_PLL_CLK);
    }

    RSI_CLK_QspiClkConfig(M4CLK, QSPI_INTFPLLCLK, 0, 0, 1);
#endif /* SWITCH_QSPI_TO_SOC_PLL */
    return status;
}
#endif // CHIP_CONFIG_ENABLE_ICD_SERVER
} // namespace

SilabsPlatform SilabsPlatform::sSilabsPlatformAbstractionManager;
SilabsPlatform::SilabsButtonCb SilabsPlatform::mButtonCallback = nullptr;

CHIP_ERROR SilabsPlatform::Init(void)
{
    mButtonCallback = nullptr;

#if CHIP_CONFIG_ENABLE_ICD_SERVER == 0
    // Configuration the clock rate
    soc_pll_config();
#endif // CHIP_CONFIG_ENABLE_ICD_SERVER

#if SILABS_LOG_ENABLED
    silabsInitLog();
#endif

#if SILABS_LOG_OUT_UART || ENABLE_CHIP_SHELL
    uartConsoleInit();
#endif

#ifdef SL_CATALOG_SYSTEMVIEW_TRACE_PRESENT
    SEGGER_SYSVIEW_Conf();
#endif

    return CHIP_NO_ERROR;
}

#ifdef ENABLE_WSTK_LEDS
void SilabsPlatform::InitLed(void)
{
    // TODO
#if defined(SL_SI91X_BOARD_INIT)
    RSI_Board_Init();
#endif // SL_SI91X_BOARD_INIT
    SilabsPlatformAbstractionBase::InitLed();
}

CHIP_ERROR SilabsPlatform::SetLed(bool state, uint8_t led)
{
    VerifyOrReturnError(led < SL_LED_COUNT, CHIP_ERROR_INVALID_ARGUMENT);
#if (defined(SL_MATTER_RGB_LED_ENABLED) && SL_MATTER_RGB_LED_ENABLED == 1)
    (state) ? sl_si91x_simple_rgb_led_on(SL_RGB_LED_INSTANCE(led)) : sl_si91x_simple_rgb_led_off(SL_RGB_LED_INSTANCE(led));
#else
    (state) ? sl_si91x_led_set(ledPinArray[led]) : sl_si91x_led_clear(ledPinArray[led]);
#endif // (defined(SL_MATTER_RGB_LED_ENABLED) && SL_MATTER_RGB_LED_ENABLED)
    return CHIP_NO_ERROR;
}

bool SilabsPlatform::GetLedState(uint8_t led)
{
    // TODO
    return SilabsPlatformAbstractionBase::GetLedState(led);
}

CHIP_ERROR SilabsPlatform::ToggleLed(uint8_t led)
{
    VerifyOrReturnError(led < SL_LED_COUNT, CHIP_ERROR_INVALID_ARGUMENT);
#if (defined(SL_MATTER_RGB_LED_ENABLED) && SL_MATTER_RGB_LED_ENABLED == 1)
    sl_si91x_simple_rgb_led_toggle(SL_RGB_LED_INSTANCE(led));
#else
    sl_si91x_led_toggle(ledPinArray[led]);
#endif // (defined(SL_MATTER_RGB_LED_ENABLED) && SL_MATTER_RGB_LED_ENABLED)
    return CHIP_NO_ERROR;
}
#endif // ENABLE_WSTK_LEDS
#if defined(SL_CATALOG_CUSTOM_MAIN_PRESENT)
void SilabsPlatform::StartScheduler()
{
    vTaskStartScheduler();
}
#endif // SL_CATALOG_CUSTOM_MAIN_PRESENT

#if (defined(SL_MATTER_RGB_LED_ENABLED) && SL_MATTER_RGB_LED_ENABLED == 1)
bool SilabsPlatform::GetRGBLedState(uint8_t led)
{
    return sl_si91x_simple_rgb_led_get_current_state(SL_RGB_LED_INSTANCE(led));
}
CHIP_ERROR SilabsPlatform::SetLedColor(uint8_t led, uint8_t red, uint8_t green, uint8_t blue)
{
    uint32_t rgb_color;
    rgb_color = (red) << 16 | (green) << 8 | (blue);
    sl_si91x_simple_rgb_led_set_colour(SL_RGB_LED_INSTANCE(led), rgb_color);
    return CHIP_NO_ERROR;
}
CHIP_ERROR SilabsPlatform::GetLedColor(uint8_t led, uint16_t & r, uint16_t & g, uint16_t & b)
{
    sl_si91x_simple_rgb_led_get_colour(SL_RGB_LED_INSTANCE(led), &r, &g, &b);
    return CHIP_NO_ERROR;
}
#endif // (defined(SL_MATTER_RGB_LED_ENABLED) && SL_MATTER_RGB_LED_ENABLED)

#ifdef SL_CATALOG_SIMPLE_BUTTON_PRESENT
extern "C" void sl_button_on_change(uint8_t btn, uint8_t btnAction)
{
#if SL_ICD_ENABLED
    // This is to make sure we get a one-press and one-release event for the button
    // Hardware modification will be required for this to work permanently
    // Currently the btn0 is pull-up resistor due to which is sends a release event on every wakeup
    if (btn == SL_BUTTON_BTN0_NUMBER)
    {
        // if the btn was not pressed and only a release event came, ignore it
        // if the btn was already pressed and another press event came, ignore it
        // essentially, if both of them are in the same state then ignore it.
        VerifyOrReturn(btnAction != btn0_pressed);

        if ((btnAction == BUTTON_PRESSED) && (btn0_pressed == false))
        {
            btn0_pressed = true;
        }
        else if ((btnAction == BUTTON_RELEASED) && (btn0_pressed == true))
        {
            btn0_pressed = false;
        }
    }
#endif // SL_ICD_ENABLED
    if (Silabs::GetPlatform().mButtonCallback == nullptr)
    {
        return;
    }

    if (btn < SL_SI91x_BUTTON_COUNT)
    {
        sButtonStates[btn] = btnAction;
    }
    Silabs::GetPlatform().mButtonCallback(btn, btnAction);
}

extern "C" void sl_si91x_button_isr(uint8_t pin, int8_t state)
{
    (pin == SL_BUTTON_BTN0_PIN) ? sl_button_on_change(SL_BUTTON_BTN0_NUMBER, state)
                                : sl_button_on_change(SL_BUTTON_BTN1_NUMBER, state);
}

uint8_t SilabsPlatform::GetButtonState(uint8_t button)
{
    return (button < SL_SI91x_BUTTON_COUNT) ? sButtonStates[button] : 0;
}
#else
uint8_t SilabsPlatform::GetButtonState(uint8_t button)
{
    return 0;
}
#endif // SL_CATALOG_SIMPLE_BUTTON_PRESENT

void SilabsPlatform::SoftwareReset()
{
    sl_si91x_soc_nvic_reset();
}

CHIP_ERROR SilabsPlatform::FlashInit()
{
    return CHIP_NO_ERROR;
}

CHIP_ERROR SilabsPlatform::FlashErasePage(uint32_t addr)
{
    rsi_flash_erase_sector((uint32_t *) addr);
    return CHIP_NO_ERROR;
}

CHIP_ERROR SilabsPlatform::FlashWritePage(uint32_t addr, const uint8_t * data, size_t size)
{
    rsi_flash_write((uint32_t *) addr, (unsigned char *) data, size);
    return CHIP_NO_ERROR;
}

} // namespace Silabs
} // namespace DeviceLayer
} // namespace chip
